Data types
SCL allows you to define your own types using the data and type keywords.
Simple product types
A product type (analogous to a struct or record in other languages) groups several
values together under a single constructor:
data Vector3d = Vector3d Double Double Double
The first Vector3d is the type name; the second is the constructor. They may
share the same name. Constructors are functions:
origin :: Vector3d
origin = Vector3d 0.0 0.0 0.0
magnitude :: Vector3d -> Double
magnitude (Vector3d x y z) = sqrt (x*x + y*y + z*z)
Union types (sum types / algebraic data types)
A union type has multiple constructors, each carrying different data:
data StringOrDouble = S String | D Double
describe :: StringOrDouble -> String
describe (S s) = "String: \(s)"
describe (D d) = "Double: \(show d)"
Each constructor can carry a different number and type of fields. Constructors with no
fields are enumeration-style:
data Direction = North | South | East | West
data Shape
= Circle Double // radius
| Rectangle Double Double // width, height
| Triangle Double Double Double
Record types
Records add named fields to a constructor, enabling field access by name:
data Circle = Circle
{ id :: String
, radius :: Double
, x :: Double
, y :: Double
}
Fields are accessed with dot notation:
area :: Circle -> Double
area c = pi * c.radius * c.radius
which requires the fields module header feature to work:
module {
features = [fields]
}
Records can be pattern-matched with named fields (unmentioned fields are ignored):
moveTo :: Double -> Double -> Circle -> Circle
moveTo newX newY Circle{ id = cid, radius = r } =
Circle { id = cid, radius = r, x = newX, y = newY }
The {..} double-dot notation binds all fields to variables of the same name:
describe :: Circle -> String
describe Circle{..} = "Circle \(id) at (\(x),\(y)) r=\(radius)"
Type aliases
type introduces a new name for an existing type without creating a new constructor.
Aliases are purely for readability; the compiler treats them as the same type:
type Point = (Double, Double)
type Name = String
distance :: Point -> Point -> Double
distance (x1, y1) (x2, y2) =
let dx = x1 - x2
dy = y1 - y2
in sqrt (dx*dx + dy*dy)
Parametric aliases are also supported:
type Pair a = (a, a)
type Vector3 a = (a, a, a)
Deriving Show
To make a custom type printable with show (and displayable in the console), derive
the Show instance:
data Color = Red | Green | Blue
deriving instance Show Color
> show Red
"Red"
> show [Red, Green, Blue]
"[Red, Green, Blue]"
deriving instance Show works for simple types. For types with fields, the derived
instance shows the constructor name and all field values.
Recursive types
Types can refer to themselves, enabling linked structures:
data Tree a = Leaf | Node a (Tree a) (Tree a)
depth :: Tree a -> Integer
depth Leaf = 0
depth (Node _ l r) = 1 + max (depth l) (depth r)
Parametric types
Type constructors can take type parameters (like generics in Java):
data Pair a b = Pair a b
swap :: Pair a b -> Pair b a
swap (Pair x y) = Pair y x
The compiler infers concrete types at each call site.
|